{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "99f74b08-3432-4327-a0f8-496f3bca30d3",
   "metadata": {},
   "source": [
    "# agent"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d389bb7d-5bf6-4fd7-9851-555ec4a795df",
   "metadata": {},
   "source": [
    "## 1 structured output and function calling"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "f0a0acbc-cc18-4ccd-aeae-2b468abe6160",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "setup='为什么猫总是喜欢坐在窗台上？' punchline='因为那里有最好的风景。' rating=8\n"
     ]
    }
   ],
   "source": [
    "# Schema for structured output\n",
    "from pydantic import BaseModel, Field\n",
    "from langchain_ollama import ChatOllama\n",
    "#llm = ChatOllama(base_url=\"http://localhost:11434\", model=\"qwen2.5:latest\")\n",
    "#llm = ChatOllama(base_url=\"http://192.168.99.142:11434\", model=\"llava:latest\")\n",
    "llm = ChatOllama(base_url=\"http://192.168.99.142:11434\", model=\"hhao/qwen2.5-coder-tools:latest\")\n",
    "\n",
    "\n",
    "# Pydantic\n",
    "class Joke(BaseModel):\n",
    "    \"\"\"Joke to tell user.\"\"\"\n",
    "\n",
    "    setup: str = Field(description=\"The setup of the joke\")\n",
    "    punchline: str = Field(description=\"The punchline to the joke\")\n",
    "    rating: Optional[int] = Field(\n",
    "        default=None, description=\"How funny the joke is, from 1 to 10\"\n",
    "    )\n",
    "    \n",
    "structured_llm = llm.with_structured_output(Joke)\n",
    "\n",
    "output = structured_llm.invoke(\"Tell me a joke about cats in chinese\")\n",
    "\n",
    "print(output)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "d3ab684c-ca97-45ea-bff8-4efa16d79673",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'name': 'multiply',\n",
       "  'args': {'a': 2, 'b': 3},\n",
       "  'id': 'd78d7dd7-5ebf-4bf2-bec4-790fdeaec5ec',\n",
       "  'type': 'tool_call'}]"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Define a tool\n",
    "def multiply(a: int, b: int) -> int:\n",
    "    return a * b\n",
    "\n",
    "# Augment the LLM with tools\n",
    "llm_with_tools = llm.bind_tools([multiply])\n",
    "\n",
    "# Invoke the LLM with input that triggers the tool call\n",
    "msg = llm_with_tools.invoke(\"What is 2 times 3?\")\n",
    "\n",
    "# Get the tool call\n",
    "msg.tool_calls"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "16a82386-ab65-4a70-a812-1c347f2495c8",
   "metadata": {},
   "source": [
    "## 2 Prompt chaining"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "id": "00a3e38c-7d3e-4fa5-bd78-299578a1d2e6",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from typing_extensions import TypedDict\n",
    "from langgraph.graph import StateGraph, START, END\n",
    "from IPython.display import Image, display\n",
    "from langchain_ollama import ChatOllama\n",
    "llm = ChatOllama(base_url=\"http://192.168.99.142:11434\", model=\"hhao/qwen2.5-coder-tools:latest\")\n",
    "# Graph state\n",
    "class State(TypedDict):\n",
    "    topic: str\n",
    "    joke: str\n",
    "    improved_joke: str\n",
    "    final_joke: str\n",
    "\n",
    "\n",
    "# Nodes\n",
    "def generate_joke(state: State):\n",
    "    \"\"\"First LLM call to generate initial joke\"\"\"\n",
    "    prompt = f\"Write a short joke about {state['topic']} and return as a string\"\n",
    "    print(prompt)\n",
    "    msg = llm.invoke(prompt)\n",
    "    print(msg)\n",
    "    return {\"joke\": msg.content}\n",
    "\n",
    "\n",
    "def check_punchline(state: State):\n",
    "    \"\"\"Gate function to check if the joke has a punchline\"\"\"\n",
    "\n",
    "    # Simple check - does the joke contain \"?\" or \"!\"\n",
    "    if \"?\" in state[\"joke\"] or \"!\" in state[\"joke\"]:\n",
    "        return \"Fail\"\n",
    "    return \"Pass\"\n",
    "\n",
    "\n",
    "def improve_joke(state: State):\n",
    "    \"\"\"Second LLM call to improve the joke\"\"\"\n",
    "\n",
    "    msg = llm.invoke(f\"Make this joke funnier by adding wordplay: {state['joke']}\")\n",
    "    return {\"improved_joke\": msg.content}\n",
    "\n",
    "\n",
    "def polish_joke(state: State):\n",
    "    \"\"\"Third LLM call for final polish\"\"\"\n",
    "\n",
    "    msg = llm.invoke(f\"Add a surprising twist to this joke: {state['improved_joke']}\")\n",
    "    return {\"final_joke\": msg.content}\n",
    "\n",
    "\n",
    "# Build workflow\n",
    "workflow = StateGraph(State)\n",
    "\n",
    "# Add nodes\n",
    "workflow.add_node(\"generate_joke\", generate_joke)\n",
    "workflow.add_node(\"improve_joke\", improve_joke)\n",
    "workflow.add_node(\"polish_joke\", polish_joke)\n",
    "\n",
    "# Add edges to connect nodes\n",
    "workflow.add_edge(START, \"generate_joke\")\n",
    "workflow.add_conditional_edges(\n",
    "    \"generate_joke\", check_punchline, {\"Fail\": \"improve_joke\", \"Pass\": END}\n",
    ")\n",
    "workflow.add_edge(\"improve_joke\", \"polish_joke\")\n",
    "workflow.add_edge(\"polish_joke\", END)\n",
    "\n",
    "# Compile\n",
    "chain = workflow.compile()\n",
    "\n",
    "# Show workflow\n",
    "display(Image(chain.get_graph().draw_mermaid_png()))\n",
    "#img = Image(chain.get_graph().draw_mermaid_png())\n",
    "#print(img)\n",
    "#display(img)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "id": "91643348-1e96-492f-b7cb-3c342353ebb2",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Write a short joke about cats and return as a string\n",
      "content=\"Sure! Here's a short joke for you:\\n\\nWhy don't scientists trust atoms?\\n\\nBecause they make up everything!\" additional_kwargs={} response_metadata={'model': 'hhao/qwen2.5-coder-tools:latest', 'created_at': '2025-04-19T10:00:26.7078376Z', 'done': True, 'done_reason': 'stop', 'total_duration': 745755600, 'load_duration': 12061500, 'prompt_eval_count': 585, 'prompt_eval_duration': 5000000, 'eval_count': 24, 'eval_duration': 724000000, 'message': Message(role='assistant', content='', images=None, tool_calls=None)} id='run-24a356d2-14ae-430d-adf4-146fc5dadc70-0' usage_metadata={'input_tokens': 585, 'output_tokens': 24, 'total_tokens': 609}\n",
      "Initial joke:\n",
      "Sure! Here's a short joke for you:\n",
      "\n",
      "Why don't scientists trust atoms?\n",
      "\n",
      "Because they make up everything!\n",
      "\n",
      "--- --- ---\n",
      "\n",
      "Improved joke:\n",
      "Here's a revised version with added wordplay:\n",
      "\n",
      "Why do scientists distrust atoms? Because atoms are the building blocks of everything, and scientists know that when you put them together, they can form some pretty explosive situations!\n",
      "\n",
      "--- --- ---\n",
      "\n",
      "Final joke:\n",
      "{\n",
      "  \"name\": \"ask_followup_question\",\n",
      "  \"arguments\": {\n",
      "    \"question\": \"Do you have any other jokes or topics you'd like to explore?\"\n",
      "  }\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "# Invoke\n",
    "state = chain.invoke({\"topic\": \"cats\"})\n",
    "print(\"Initial joke:\")\n",
    "print(state[\"joke\"])\n",
    "print(\"\\n--- --- ---\\n\")\n",
    "if \"improved_joke\" in state:\n",
    "    print(\"Improved joke:\")\n",
    "    print(state[\"improved_joke\"])\n",
    "    print(\"\\n--- --- ---\\n\")\n",
    "\n",
    "    print(\"Final joke:\")\n",
    "    print(state[\"final_joke\"])\n",
    "else:\n",
    "    print(\"Joke failed quality gate - no punchline detected!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dd608e68-ece8-44ea-8166-ecf5408d1c49",
   "metadata": {},
   "source": [
    "## 3 Parallelization"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "id": "4722f056-71b6-471a-8cc1-06dabd96e8b7",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Graph state\n",
    "class State(TypedDict):\n",
    "    topic: str\n",
    "    joke: str\n",
    "    story: str\n",
    "    poem: str\n",
    "    combined_output: str\n",
    "from langchain_ollama import ChatOllama\n",
    "#llm = ChatOllama(base_url=\"http://192.168.99.142:11434\", model=\"hhao/qwen2.5-coder-tools:latest\")\n",
    "llm = ChatOllama(base_url=\"http://192.168.99.142:11434\", model=\"deepseek-r1:7b\")\n",
    "\n",
    "\n",
    "# Nodes\n",
    "def call_llm_1(state: State):\n",
    "    \"\"\"First LLM call to generate initial joke\"\"\"\n",
    "\n",
    "    msg = llm.invoke(f\"Write a short joke about {state['topic']} and return as string\")\n",
    "    return {\"joke\": msg.content}\n",
    "\n",
    "\n",
    "def call_llm_2(state: State):\n",
    "    \"\"\"Second LLM call to generate story\"\"\"\n",
    "\n",
    "    msg = llm.invoke(f\"Write a story about {state['topic']}\")\n",
    "    return {\"story\": msg.content}\n",
    "\n",
    "\n",
    "def call_llm_3(state: State):\n",
    "    \"\"\"Third LLM call to generate poem\"\"\"\n",
    "\n",
    "    msg = llm.invoke(f\"Write a poem about {state['topic']}\")\n",
    "    return {\"poem\": msg.content}\n",
    "\n",
    "\n",
    "def aggregator(state: State):\n",
    "    \"\"\"Combine the joke and story into a single output\"\"\"\n",
    "\n",
    "    combined = f\"Here's a story, joke, and poem about {state['topic']}!\\n\\n\"\n",
    "    combined += f\"STORY:\\n{state['story']}\\n\\n\"\n",
    "    combined += f\"JOKE:\\n{state['joke']}\\n\\n\"\n",
    "    combined += f\"POEM:\\n{state['poem']}\"\n",
    "    return {\"combined_output\": combined}\n",
    "\n",
    "\n",
    "# Build workflow\n",
    "parallel_builder = StateGraph(State)\n",
    "\n",
    "# Add nodes\n",
    "parallel_builder.add_node(\"call_llm_1\", call_llm_1)\n",
    "parallel_builder.add_node(\"call_llm_2\", call_llm_2)\n",
    "parallel_builder.add_node(\"call_llm_3\", call_llm_3)\n",
    "parallel_builder.add_node(\"aggregator\", aggregator)\n",
    "\n",
    "# Add edges to connect nodes\n",
    "parallel_builder.add_edge(START, \"call_llm_1\")\n",
    "parallel_builder.add_edge(START, \"call_llm_2\")\n",
    "parallel_builder.add_edge(START, \"call_llm_3\")\n",
    "parallel_builder.add_edge(\"call_llm_1\", \"aggregator\")\n",
    "parallel_builder.add_edge(\"call_llm_2\", \"aggregator\")\n",
    "parallel_builder.add_edge(\"call_llm_3\", \"aggregator\")\n",
    "parallel_builder.add_edge(\"aggregator\", END)\n",
    "parallel_workflow = parallel_builder.compile()\n",
    "\n",
    "# Show workflow\n",
    "display(Image(parallel_workflow.get_graph().draw_mermaid_png()))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "id": "1b0c197e-0042-4326-ab98-37da2a036ded",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Here's a story, joke, and poem about cats!\n",
      "\n",
      "STORY:\n",
      "<think>\n",
      "Okay, so I need to write a story about cats. Hmm, where do I start? Well, stories usually have characters, setting, and plot. Let me think about what elements make a cat story engaging.\n",
      "\n",
      "Maybe I can set it in a home because that's relatable. The cat could be a beloved pet, like a kitten. I should give the cat a name; maybe something friendly like Whiskers or Fluffy? Then there's the setting—a living room with soft furniture and maybe a window where cats are commonly seen.\n",
      "\n",
      "The plot needs to have some conflict but also show growth. Perhaps introduce a problem that challenges the cat, leading to personal development. Maybe Fluffy faces a challenge at school or something similar for adult cats too.\n",
      "\n",
      "I should include some interactions between the cat and its owner, showing the bond. Also, maybe include other animals like a dog or another pet to add depth. The story can have a resolution where the cat overcomes the problem with help from home or friends.\n",
      "\n",
      "Wait, I remember that sometimes stories about cats can also explore deeper themes, like kindness or overcoming adversity. Maybe Fluffy learns something important through this experience.\n",
      "\n",
      "I should outline the story: introduction of the characters and setting, the inciting incident (the challenge), the journey to overcome it, the climax where the problem is resolved, and then a resolution showing growth or new understanding.\n",
      "\n",
      "Let me think about the structure:\n",
      "\n",
      "1. Introduction: Meet Fluffy in his usual spot.\n",
      "2. Inciting Incident: A challenge that comes up—maybe meeting another animal.\n",
      "3. Conflict: How Fluffy handles this challenge.\n",
      "4. Climax: Overcoming the challenge with help from others.\n",
      "5. Resolution: Fluffy grows, perhaps learning something new.\n",
      "\n",
      "I need to make sure each part flows into the next and keep the reader engaged. Descriptions should be vivid so that I can paint a picture in their mind.\n",
      "\n",
      "Also, including dialogue can add interaction between characters, making the story more dynamic.\n",
      "\n",
      "Wait, do I want this to focus on Fluffy's growth as a cat or her relationships with others? Probably both—showing how she interacts and learns from others.\n",
      "\n",
      "Maybe include a scene where another animal, like a dog or even an insect, helps Fluffy learn something. That adds variety.\n",
      "\n",
      "I should also consider the ending. It shouldn't be sad; instead, it should show that Fluffy has become stronger or more understanding, which can bring a sense of closure and positive resolution.\n",
      "\n",
      "Alright, I think I have a rough outline in my mind. Now, time to put it all together into a coherent story.\n",
      "</think>\n",
      "\n",
      "**Title: The Tale of Whiskers**\n",
      "\n",
      "Once upon a time in a cozy home on a sunny street, lived a lively kitten named Whiskers. Whiskers was known for her bright eyes and playful spirit, often lounging by the window where she could watch birds outside her glass pane.\n",
      "\n",
      "One crisp autumn afternoon, as Whiskers lazily stretched her paws, she heard the sound of muffled clicks coming from outside. Intrigued, she padded out of her window, only to find a small dog, a mischievous mutt named Max, stuck in a tall tree. Whiskers felt a twinge of guilt for her favorite leaf spot, but her curiosity piqued; she dispatched her owner with a playful yip.\n",
      "\n",
      "Max was trembling and panting, his ears flopping frantically. As Whiskers reached the top, she rescued him just as he tripped on a branch, scraping his collar. Max barked Managingly, \"Please, no more!\" His fur matted from the injury, and his tail wailed with distress.\n",
      "\n",
      "Whiskers gently petted his ears, offering comfort. \"Don't worry, buddy,\" she soothe. As they descended together, Max showed her a shiny red ball he had found. \"Can I play with that?\" He beckoned, pulling out an polished steel sphere.\n",
      "\n",
      "Whiskers hesitated but eventually agreed. With a roll of her tongue, she facilitated the transfer to their tree, where Max joyfully bounced the ball, chasing it between their branches. Though Max's injury worsened, Whiskers stayed by his side, now acting as his protector and helper.\n",
      "\n",
      "In return, Max taught Whiskers new tricks, such as howling with more volume for attention or pressing her back to receive affection. Their bond deepened, with Whiskers learning empathy from Max's need for care and vice versa.\n",
      "\n",
      "As seasons changed, so did their companionship. Winter brought cold nips, but they faced them together, adapting to the environment. Whiskers' playfulness transformed into gentle strokes, while Max's loyalty became an comforting presence in her afternoons.\n",
      "\n",
      "One day, news arrived—a new puppy was introduced to the neighborhood. The adults jokingly spoke of the puppy's arrival and potential conflict with Max. Concerned for Whiskers, the parents suggested she join Max at his kennel. Reluctantly, Whiskers agreed, yet her heart ached as she left her familiar window.\n",
      "\n",
      "In the kennel, Max greeted the new arrival with excitement, showing him off to visitors. Whiskers felt displaced, missing her cozy window and the interactions with Max. However, she adapted by curling up in Max's lap, allowing him to hold her near the new puppy.\n",
      "\n",
      "Gradually, Whiskers began to adjust, finding comfort in Max's presence even as she adapted to life beyond the window. She learned to share more deeply, understanding that life's changes often brought opportunities for growth and learning.\n",
      "\n",
      "Years passed, and Whiskers grew into a mature cat, now twenty-one. Her story was one of resilience, showing how cats can find strength through community support, friendships, and self-acceptance—even when faced with change.\n",
      "\n",
      "And so, Whiskers flourished, her tail wagging with joy as she navigated life's journey alongside Max and the new puppy, all while cherished by her loving owner. She was a testament to the adaptability of cats and the bonds that brought them together.\n",
      "\n",
      "The end.\n",
      "\n",
      "JOKE:\n",
      "<think>\n",
      "Okay, so I need to write a short joke about cats and then provide it as a string. Let me think about this.\n",
      "\n",
      "First, I should recall some common cat-related puns or wordplay because jokes often rely on that. Words like \"purr\" instead of \"pure,\" \"whiskers,\" or \"nine lives.\" Maybe something involving their behavior or characteristics.\n",
      "\n",
      "I remember a classic one: \"Why did the cat climb the ladder? Because it wanted to be a high-pitchedowel!\" That's a play on words with \"high-pitched\" and \"high-five.\"\n",
      "\n",
      "Wait, that works. It uses \"purr\" as \"pure\" but also plays with \"high-pitched,\" which is a pun. I think that could be a good joke.\n",
      "\n",
      "So putting it all together: Why did the cat climb the ladder? To get to the high-pitchedowel! That should do it.\n",
      "</think>\n",
      "\n",
      "Why did the cat climb the ladder?  \n",
      "To get to the high-pitchedowel!\n",
      "\n",
      "POEM:\n",
      "<think>\n",
      "Okay, so the user asked me to write a poem about cats. I need to come up with something creative that captures the essence of cats.\n",
      "\n",
      "First, I'll think about what makes cats unique—their agility, independence, and playful nature. Maybe start by highlighting their presence in households without needing too much description.\n",
      "\n",
      "I should use imagery that's vivid but not overcomplicated. Words like \"sneak,\" \"whiskers,\" and \"paws\" could be good to include. Also, maybe some metaphors or personifications to give them a bit of character.\n",
      "\n",
      "The poem structure... I'm leaning towards quatrains with an AABB rhyme scheme for simplicity. That should make it flow nicely without being too rigid.\n",
      "\n",
      "Let me outline a few stanzas. Maybe one about their arrival and presence, another about their interactions, maybe playfulness, independence, and how they adapt to life. Each stanza can have four lines, following the rhyme scheme.\n",
      "\n",
      "I'll need to keep each line concise but evocative. Maybe include some sensory details like sounds or smells associated with cats—like a soft purr or the scent of claw.\n",
      "\n",
      "Also, I should think about the rhythm. The poem should have a consistent beat so it's easy to read and flows well when spoken aloud.\n",
      "\n",
      "Let me try putting these thoughts together into a few verses. Each verse will focus on a different aspect of the cat's character—starting with their arrival, moving to their playful side, independence, and how they're part of the family without needing too much attention.\n",
      "\n",
      "I should avoid making it too long or too complex for the average reader. It needs to be relatable and engaging.\n",
      "\n",
      "Alright, putting it all together now... I'll draft each stanza step by step, ensuring that each couplet rhymes and builds upon the previous one.\n",
      "</think>\n",
      "\n",
      "In a flick of shadowed grace, they land and rest,  \n",
      "Cats in our homes, with no need for best.  \n",
      "A gentle paw, a soft nudge, but far too stealthy to hide,  \n",
      "These feline friends walk where we’ve not yet been.\n",
      "\n",
      "With eyes that twinkle like stars on dark night,  \n",
      "They purr when playful, when it’s time to rest.  \n",
      "In the chaos of our lives, in the laughter and games,  \n",
      "Cats find their way into every home we make.\n",
      "\n",
      "So let us give them a warm embrace, with open arms,  \n",
      "To share this life in quiet moments just.  \n",
      "Not needing much at all, yet so deeply missed when gone,  \n",
      "These agile creatures bring joy to our hearts.\n",
      "\n",
      "With no need for food, yet plenty we have plenty of treats,  \n",
      "In their playful ways they teach us, teach us all.  \n",
      "From sitting on laps to climbing through tallest trees,  \n",
      "Cats show what it means to live life with freedom and ease.\n",
      "\n",
      "So let us love them now, embrace this sweet bond,  \n",
      "With pets in our lives, we’ve found a perfect blend.  \n",
      "In their tails of light, their voices so clear,  \n",
      "Our days are brighter because of these feline friends near.\n"
     ]
    }
   ],
   "source": [
    "# Invoke\n",
    "state = parallel_workflow.invoke({\"topic\": \"cats\"})\n",
    "print(state[\"combined_output\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "79a5039b-95a3-4056-a626-7e52cfe9792d",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
